package com.atguigu.gulimall.product.service.impl;

import com.atguigu.gulimall.product.dao.CategoryDao;
import com.atguigu.gulimall.product.entity.BrandEntity;
import com.atguigu.gulimall.product.entity.CategoryBrandRelationEntity;
import com.atguigu.gulimall.product.entity.CategoryEntity;
import com.atguigu.gulimall.product.service.BrandService;
import com.atguigu.gulimall.product.service.CategoryBrandRelationService;
import com.atguigu.gulimall.product.service.CategoryService;
import com.atguigu.gulimall.product.vo.Catalog2Vo;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    private CategoryBrandRelationService categoryBrandRelationService;

    @Autowired
    private CategoryServiceImpl categoryServiceImpl;

    @Autowired
    private BrandService brandService;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Override
    public List<CategoryEntity> queryTreeList() {
        List<CategoryEntity> entities = baseMapper.selectList(null);
        return entities.stream().filter(category -> Objects.equals(category.getParentCid(), 0L)).peek(categoryEntity -> categoryEntity.setChildren(getChildren(categoryEntity, entities))).sorted(Comparator.comparing(CategoryEntity::getSort)).collect(Collectors.toList());
    }

    @Override
    public void removeMenuByIds(List<Long> catIds) {
        //TODO 检查当前菜单是否被其他地方引用

        //逻辑删除
        baseMapper.deleteBatchIds(catIds);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateByList(List<CategoryEntity> categoryList) {
        this.updateBatchById(categoryList);
    }

    /**
     * 递归查找当前菜单的子菜单(CategoryEntity中的sort字段可能为null，做置0处理)
     */
    private List<CategoryEntity> getChildren(CategoryEntity categoryEntity, List<CategoryEntity> entities) {
        return entities.stream().filter(category -> Objects.equals(category.getParentCid(), categoryEntity.getCatId())).peek(category -> {
                    category.setChildren(getChildren(category, entities));
                    category.setSort(category.getSort() == null ? 0 : category.getSort());
                }
        ).sorted(Comparator.comparing(CategoryEntity::getSort)).collect(Collectors.toList());
    }

    @Override
    public Long[] getCatelogIds(Long catelogId) {
        List<Long> categoryPaths = new ArrayList<>();
        List<Long> categoryIds = findCatelogIds(catelogId, categoryPaths);
        Collections.reverse(categoryIds);
        return categoryIds.toArray(new Long[0]);
    }

    private List<Long> findCatelogIds(Long catelogId, List<Long> categoryPaths) {
        categoryPaths.add(catelogId);
        CategoryEntity curCategory = this.getById(catelogId);
        if (curCategory.getParentCid() != 0) {
            this.findCatelogIds(curCategory.getParentCid(), categoryPaths);
        }
        return categoryPaths;
    }

    @Override
    //不影响方法执行 更新缓存 但是需要方法有返回值
    //@CachePut
    @Transactional(rollbackFor = Exception.class)
    // 聚合多个删除逻辑
    //@Caching(evict = {
    //        @CacheEvict(cacheNames = "Category",key = "'queryTopCategoryList'"),
    //        @CacheEvict(cacheNames = "Category",key = "'getCatalogJson'"),
    //})
    //直接删除CacheManager下的所有cache
    @CacheEvict(cacheNames = "Category", allEntries = true)
    public void updateDetail(CategoryEntity category) {
        this.updateById(category);
        if (StringUtils.isBlank(category.getName())) {
            return;
        }
        this.categoryBrandRelationService.update(CategoryBrandRelationEntity.builder().catelogId(category.getCatId()).catelogName(category.getName()).build(), new UpdateWrapper<CategoryBrandRelationEntity>().eq("catelog_id", category.getCatId()));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void transactionalJunitTest() {
        try {
            this.save(CategoryEntity.builder().name("事务测试主方法").build());
            brandService.saveTransactional(BrandEntity.builder().name("事务测试子类方法").build());
            slaveTransactionalMethod();
        } catch (Exception e) {
            throw new RuntimeException();
        }
    }

    @Override
    @Cacheable(cacheNames = "Category",key = "#root.methodName")
    public List<CategoryEntity> queryTopCategoryList() {
        log.info("queryTopCategoryList：{}", Thread.currentThread().getId());
        return this.list(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
    }

    @Override
    @Cacheable(cacheNames = "Category", key = "#root.methodName", sync = true)
    public Map<String, List<Catalog2Vo>> getCatalogJson() {
        // 使用SpringCache 无法自动实现分布式锁，只能加本地锁
        List<CategoryEntity> entities = this.queryTreeList();
        List<Catalog2Vo> catalog2VoList = new ArrayList<>();
        entities.forEach(top -> {
            if (CollectionUtils.isNotEmpty(top.getChildren())) {
                top.getChildren().forEach(second -> {
                    catalog2VoList.add(new Catalog2Vo(top.getCatId().toString(), second.getCatId().toString(), second.getName(), getThirdList(second.getCatId(), second.getChildren())));
                });
            }
        });
        return catalog2VoList.stream().collect(Collectors.groupingBy(Catalog2Vo::getCatalog1Id));

        /* 使用Redisson 实现分布式锁 */
        //String catalogJson = stringRedisTemplate.opsForValue().get("catalogJson");
        //if (StringUtils.isNotBlank(catalogJson)) {
        //    return JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {});
        //}
        //RLock catalogJosnLock = redissonClient.getLock("catalogJson-lock");
        //catalogJosnLock.lock();
        //try {
        //    catalogJson = stringRedisTemplate.opsForValue().get("catalogJson");
        //    if (StringUtils.isNotBlank(catalogJson)) {
        //        return JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {});
        //    }
        //    List<CategoryEntity> entities = this.queryTreeList();
        //    List<Catalog2Vo> catalog2VoList = new ArrayList<>();
        //    entities.forEach(top -> {
        //        if (CollectionUtils.isNotEmpty(top.getChildren())) {
        //            top.getChildren().forEach(second -> {
        //                catalog2VoList.add(new Catalog2Vo(top.getCatId().toString(), second.getCatId().toString(), second.getName(), getThirdList(second.getCatId(), second.getChildren())));
        //            });
        //        }
        //    });
        //    return catalog2VoList.stream().collect(Collectors.groupingBy(Catalog2Vo::getCatalog1Id));
        //} catch (Exception e) {
        //    e.printStackTrace();
        //} finally {
        //    catalogJosnLock.unlock();
        //}
        //throw new RuntimeException("处理三级分类数据异常");

        /* 最普通的 手动实现redis分布式锁 */
        //String randomUuid = UUID.randomUUID().toString();
        ///*TODO 可以在业务执行中，动态增加锁的过期时间 (这一个问题在Redisson中帮我们解决了  - 看门狗，锁的自动续期，默认加的锁都是30秒)*/
        //Boolean isGetLock = stringRedisTemplate.opsForValue().setIfAbsent("catalogJson-lock", randomUuid, 30, TimeUnit.SECONDS);
        //if (BooleanUtils.isTrue(isGetLock) && StringUtils.isBlank(stringRedisTemplate.opsForValue().get("catalogJson"))) {
        //    try {
        //        List<CategoryEntity> entities = this.queryTreeList();
        //        List<Catalog2Vo> catalog2VoList = new ArrayList<>();
        //        entities.forEach(top -> {
        //            if (CollectionUtils.isNotEmpty(top.getChildren())) {
        //                top.getChildren().forEach(second -> {
        //                    catalog2VoList.add(new Catalog2Vo(top.getCatId().toString(), second.getCatId().toString(), second.getName(), getThirdList(second.getCatId(), second.getChildren())));
        //                });
        //            }
        //        });
        //        Map<String, List<Catalog2Vo>> catalogJsonFromDb = catalog2VoList.stream().collect(Collectors.groupingBy(Catalog2Vo::getCatalog1Id));
        //        stringRedisTemplate.opsForValue().set("catalogJson", JSON.toJSONString(catalogJsonFromDb));
        //        return catalogJsonFromDb;
        //    } catch (Exception e) {
        //        e.printStackTrace();
        //    } finally {
        //        stringRedisTemplate.execute(new DefaultRedisScript<>("if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end", Long.class), Collections.singletonList("catalogJson-lock"), randomUuid);
        //    }
        //    throw new RuntimeException("处理三级分类数据异常");
        //} else {
        //    //自旋
        //    Thread.sleep(200);
        //    return getCatalogJson();
        //}
    }

    private List<Catalog2Vo.Catalog3Vo> getThirdList(Long catalog2Id, List<CategoryEntity> catalog3VoList) {
        List<Catalog2Vo.Catalog3Vo> catalog3Vos = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(catalog3VoList)) {
            catalog3VoList.forEach(item -> catalog3Vos.add(new Catalog2Vo.Catalog3Vo(catalog2Id.toString(), item.getCatId().toString(), item.getName())));
            return catalog3Vos;
        }
        return catalog3Vos;
    }

    public void slaveTransactionalMethod() {
        this.save(CategoryEntity.builder().name("事务测试子方法").build());
        throw new ArithmeticException("同类间方法调用-测试回滚");
    }
}
